/**
 * 封装一个对象迭代的方法
 * 基于传统的for/in循环存在一些弊端，即可以迭代私有的属性也可以迭代公有的属性，导致性能较差；只能迭代可枚举 非Symbol类型的属性
 * 
 * 结局思路： 
 *  @1 获取对象的所有私有属性（无论是否可以枚举，无论哪种类型）
 *    Object.getOwnPropertyNames()==>获取对象所有的非Symbol类型的私有属性（无关是否可枚举）
 *    Object.getOwnPropertySymbols()==>获取Symboll类型的私有属性
 *    let keys = Object.getOwnPropertyNames().concat(Object.getOwnPropertySymbols())
 *  @2 也可以使用ES6中的Reflect.ownKeys()代替以上操作（弊端是不兼容IE）
 */
const each = function each(obj, callback) {
  if (obj === null || typeof(obj) !== 'object') throw new TypeError('obj is not a object');
  if (typeof(callback) !== 'function') throw new TypeError('callback is not a function');
  let keys = Reflect.ownKeys(obj);
  keys.forEach((key) => {
    callback(key, obj[key]);
  });
};

/**
 * createElement
 * 创建虚拟DOM对象 */
export function createElement (ele, props, ...children) {
  let virtualDom = {
    $$typeof: Symbol('react.element'),
    key: null,
    ref: null,
    type: null,
    props: {},
  }
  virtualDom.type = ele;
  if (props !== null) {
    virtualDom.props = {
      ...props,
    };
  };
  let len = children.length;
  if (len === 1) virtualDom.props.children = children[0];
  if (len > 1) virtualDom.props.children = children;
  return virtualDom;
};

/**
 * 基于ReactDOM中的render方法将构建的virtualDom渲染为真实Dom
 * @1: React16
 *    ReactDOM.render(<>...</>, document.getELementById('root'))
 * 
 * @2: React18
 *    const root = ReactDOM.createRoot(document.getElementById('root'))
 *    root.render(<>...</>)
 *  
 * render
 * 将虚拟DOM变为真实DOM  
 */
export function render (virtualDom, container) {
  let { type, props } = virtualDom;
  if (typeof(type) === 'string') {
    // 存储的是标签名，动态的创建这样一个标签
    let ele = document.createElement(type);
    // 为标签设置相关的属性 & 子节点
    each(props, (key, value) => {
      // className的处理，value存储的是样式类名
      if (key === 'className') {
        ele.className = value;
        return;
      }

      // style的处理，value存储的是样式对象
      if (key === 'style') {
        each(value, (attr, val) => {
          ele.style[attr] = val;
        })
        return;
      }

      // 子节点的处理, value存储的是children属性值
      if (key === 'children') {
        let children = value;
        if (!Array.isArray(children)) children = [children];
        children.forEach((child) => {
          //子节点是文本节点，直接插入就行
          if (/^(string|number)$/.test(typeof(child))) {
            ele.appendChild(document.createTextNode(child));
            return;
          }
          //子节点又是一个virtualDom，递归处理
          render(child, ele);
        })
        return;
      }

      ele.setAttribute(key, value);
    })
    //把新增的标签添加到指定容器中
    container.appendChild(ele);
  }
};

